"""
配置管理器
处理设备配置的加载、保存和验证
"""

import os
import json
import logging
import shutil
from datetime import datetime
from typing import Dict, Any, List, Optional
from pathlib import Path

class ConfigManager:
    """配置管理器类"""
    
    def __init__(self, config_dir: str = 'config'):
        self.config_dir = Path(config_dir)
        self.devices_file = self.config_dir / 'devices.json'
        self.backup_dir = self.config_dir / 'backups'
        
        # 确保目录存在
        self.config_dir.mkdir(exist_ok=True)
        self.backup_dir.mkdir(exist_ok=True)
        
        self.logger = logging.getLogger(self.__class__.__name__)
    
    def load_devices(self) -> List[Dict[str, Any]]:
        """加载设备配置"""
        try:
            if not self.devices_file.exists():
                self.logger.warning(f"设备配置文件不存在: {self.devices_file}")
                return []
            
            with open(self.devices_file, 'r', encoding='utf-8') as f:
                data = json.load(f)
                if isinstance(data, list):
                    return data
                elif isinstance(data, dict) and 'devices' in data:
                    return data['devices']
                else:
                    self.logger.error("设备配置文件格式错误")
                    return []
        except Exception as e:
            self.logger.error(f"加载设备配置失败: {e}")
            return []
    
    def save_devices(self, devices: List[Dict[str, Any]], create_backup: bool = True) -> bool:
        """保存设备配置"""
        try:
            # 创建备份
            if create_backup and self.devices_file.exists():
                self._create_backup()
            
            # 保存配置
            config_data = {
                "devices": devices,
                "last_updated": datetime.now().isoformat(),
                "version": "1.0"
            }
            
            with open(self.devices_file, 'w', encoding='utf-8') as f:
                json.dump(config_data, f, indent=2, ensure_ascii=False)
            
            self.logger.info(f"成功保存 {len(devices)} 个设备配置")
            return True
            
        except Exception as e:
            self.logger.error(f"保存设备配置失败: {e}")
            return False
    
    def validate_devices_config(self, devices: List[Dict[str, Any]]) -> List[str]:
        """验证设备配置"""
        errors = []
        device_names = set()
        
        for i, device in enumerate(devices):
            try:
                # 检查必填字段
                required_fields = ['name', 'type', 'enabled', 'polling_interval']
                for field in required_fields:
                    if field not in device:
                        errors.append(f"设备 {i+1}: 缺少必填字段 '{field}'")
                        continue
                
                # 检查设备名称唯一性
                name = device.get('name', '')
                if not name:
                    errors.append(f"设备 {i+1}: 设备名称不能为空")
                elif name in device_names:
                    errors.append(f"设备 {i+1}: 设备名称 '{name}' 重复")
                else:
                    device_names.add(name)
                
                # 检查协议类型
                device_type = device.get('type', '')
                if device_type not in ['modbus', 's7', 'mqtt', 'opcua']:
                    errors.append(f"设备 {name}: 不支持的协议类型 '{device_type}'")
                
                # 检查采集间隔
                polling_interval = device.get('polling_interval', 0)
                if not isinstance(polling_interval, int) or polling_interval < 100:
                    errors.append(f"设备 {name}: 采集间隔必须是大于100的整数")
                
                # 检查协议配置
                config = device.get('config', {})
                if not isinstance(config, dict):
                    errors.append(f"设备 {name}: 协议配置必须是对象")
                else:
                    config_errors = self._validate_protocol_config(device_type, config)
                    errors.extend([f"设备 {name}: {error}" for error in config_errors])
                
                # 检查数据点配置
                points = device.get('points', [])
                if not isinstance(points, list):
                    errors.append(f"设备 {name}: 数据点配置必须是数组")
                else:
                    point_errors = self._validate_points_config(device_type, points)
                    errors.extend([f"设备 {name}: {error}" for error in point_errors])
                
            except Exception as e:
                errors.append(f"设备 {i+1}: 配置验证异常 - {str(e)}")
        
        return errors
    
    def _validate_protocol_config(self, device_type: str, config: Dict[str, Any]) -> List[str]:
        """验证协议配置"""
        errors = []
        
        if device_type == 'modbus':
            if 'ip' not in config or not config['ip']:
                errors.append("Modbus配置缺少IP地址")
            if 'port' not in config or not isinstance(config['port'], int):
                errors.append("Modbus配置缺少有效端口")
                
        elif device_type == 's7':
            if 'ip' not in config or not config['ip']:
                errors.append("S7配置缺少IP地址")
                
        elif device_type == 'mqtt':
            if 'broker' not in config or not config['broker']:
                errors.append("MQTT配置缺少Broker地址")
            if 'port' not in config or not isinstance(config['port'], int):
                errors.append("MQTT配置缺少有效端口")
                
        elif device_type == 'opcua':
            if 'endpoint' not in config or not config['endpoint']:
                errors.append("OPC UA配置缺少端点URL")
        
        return errors
    
    def _validate_points_config(self, device_type: str, points: List[Dict[str, Any]]) -> List[str]:
        """验证数据点配置"""
        errors = []
        point_names = set()
        
        for i, point in enumerate(points):
            try:
                # 检查必填字段
                if 'name' not in point or not point['name']:
                    errors.append(f"数据点 {i+1}: 点名不能为空")
                    continue
                
                point_name = point['name']
                
                # 检查点名唯一性
                if point_name in point_names:
                    errors.append(f"数据点 '{point_name}': 点名重复")
                else:
                    point_names.add(point_name)
                
                # 检查数据类型
                if 'data_type' not in point:
                    errors.append(f"数据点 '{point_name}': 缺少数据类型")
                elif point['data_type'] not in ['bool', 'int16', 'int32', 'float', 'string']:
                    errors.append(f"数据点 '{point_name}': 不支持的数据类型")
                
                # 检查地址/节点ID
                if device_type == 'opcua':
                    if 'node_id' not in point or not point['node_id']:
                        errors.append(f"数据点 '{point_name}': OPC UA点缺少节点ID")
                elif device_type == 'mqtt':
                    if 'topic' not in point or not point['topic']:
                        errors.append(f"数据点 '{point_name}': MQTT点缺少主题")
                else:
                    if 'address' not in point or not point['address']:
                        errors.append(f"数据点 '{point_name}': 缺少地址")
                
            except Exception as e:
                errors.append(f"数据点 {i+1}: 配置验证异常 - {str(e)}")
        
        return errors
    
    def _create_backup(self) -> str:
        """创建配置备份"""
        timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
        backup_file = self.backup_dir / f"devices_{timestamp}.json"
        
        try:
            shutil.copy2(self.devices_file, backup_file)
            self.logger.info(f"创建配置备份: {backup_file}")
            return str(backup_file)
        except Exception as e:
            self.logger.error(f"创建配置备份失败: {e}")
            return ""
    
    def get_backup_list(self) -> List[Dict[str, Any]]:
        """获取备份文件列表"""
        backups = []
        try:
            for file in self.backup_dir.glob("devices_*.json"):
                stat = file.stat()
                backups.append({
                    'filename': file.name,
                    'path': str(file),
                    'size': stat.st_size,
                    'created_time': datetime.fromtimestamp(stat.st_ctime).isoformat(),
                    'modified_time': datetime.fromtimestamp(stat.st_mtime).isoformat()
                })
            
            # 按创建时间倒序排列
            backups.sort(key=lambda x: x['created_time'], reverse=True)
            
        except Exception as e:
            self.logger.error(f"获取备份列表失败: {e}")
        
        return backups
    
    def restore_from_backup(self, backup_filename: str) -> bool:
        """从备份恢复配置"""
        try:
            backup_file = self.backup_dir / backup_filename
            if not backup_file.exists():
                self.logger.error(f"备份文件不存在: {backup_file}")
                return False
            
            # 验证备份文件
            with open(backup_file, 'r', encoding='utf-8') as f:
                backup_data = json.load(f)
            
            if isinstance(backup_data, dict) and 'devices' in backup_data:
                devices = backup_data['devices']
            elif isinstance(backup_data, list):
                devices = backup_data
            else:
                self.logger.error("备份文件格式错误")
                return False
            
            # 验证配置
            errors = self.validate_devices_config(devices)
            if errors:
                self.logger.error(f"备份文件配置验证失败: {errors}")
                return False
            
            # 恢复配置
            return self.save_devices(devices, create_backup=True)
            
        except Exception as e:
            self.logger.error(f"恢复备份失败: {e}")
            return False
    
    def delete_device(self, device_name: str) -> bool:
        """删除设备配置"""
        try:
            devices = self.load_devices()
            original_count = len(devices)
            
            # 过滤掉指定设备
            devices = [device for device in devices if device.get('name') != device_name]
            
            if len(devices) == original_count:
                self.logger.warning(f"设备 {device_name} 不存在")
                return False
            
            # 保存更新后的配置
            success = self.save_devices(devices, create_backup=True)
            if success:
                self.logger.info(f"成功删除设备配置: {device_name}")
            
            return success
            
        except Exception as e:
            self.logger.error(f"删除设备配置失败: {e}")
            return False
    
    def add_device(self, device: Dict[str, Any]) -> bool:
        """添加新设备配置"""
        devices = self.load_devices()
        
        # 检查设备名称是否已存在
        existing_names = {d.get('name') for d in devices}
        if device.get('name') in existing_names:
            return False
        
        devices.append(device)
        return self.save_devices(devices)
    
    def get_device_by_name(self, device_name: str) -> Optional[Dict[str, Any]]:
        """根据名称获取设备配置"""
        devices = self.load_devices()
        for device in devices:
            if device.get('name') == device_name:
                return device
        return None
    
    def update_device(self, device_name: str, updated_device: Dict[str, Any]) -> bool:
        """更新单个设备配置"""
        devices = self.load_devices()
        
        for i, device in enumerate(devices):
            if device.get('name') == device_name:
                devices[i] = updated_device
                return self.save_devices(devices)
        
        return False
    
    def export_config(self, export_path: str) -> bool:
        """导出配置文件"""
        try:
            devices = self.load_devices()
            config = {
                "devices": devices,
                "exported_at": datetime.now().isoformat(),
                "version": "1.0"
            }
            
            with open(export_path, 'w', encoding='utf-8') as f:
                json.dump(config, f, ensure_ascii=False, indent=2)
            
            self.logger.info(f"导出配置到: {export_path}")
            return True
            
        except Exception as e:
            self.logger.error(f"导出配置失败: {e}")
            return False
    
    def import_config(self, import_path: str, merge: bool = False) -> tuple[bool, List[str]]:
        """导入配置文件"""
        try:
            with open(import_path, 'r', encoding='utf-8') as f:
                config = json.load(f)
            
            imported_devices = config.get('devices', [])
            if not imported_devices:
                return False, ["导入文件中没有设备配置"]
            
            # 验证导入的配置
            validation_errors = self.validate_devices_config(imported_devices)
            if validation_errors:
                return False, validation_errors
            
            if merge:
                # 合并模式：添加到现有配置
                current_devices = self.load_devices()
                current_names = {d['name'] for d in current_devices}
                
                # 避免重名
                merged_devices = current_devices[:]
                for device in imported_devices:
                    original_name = device['name']
                    counter = 1
                    while device['name'] in current_names:
                        device['name'] = f"{original_name}_{counter}"
                        counter += 1
                    
                    merged_devices.append(device)
                    current_names.add(device['name'])
                
                devices_to_save = merged_devices
            else:
                # 替换模式：完全替换现有配置
                devices_to_save = imported_devices
            
            # 保存配置
            success = self.save_devices(devices_to_save)
            if success:
                self.logger.info(f"导入配置成功，共 {len(imported_devices)} 个设备")
                return True, []
            else:
                return False, ["保存导入的配置失败"]
                
        except json.JSONDecodeError as e:
            return False, [f"配置文件格式错误: {str(e)}"]
        except Exception as e:
            self.logger.error(f"导入配置失败: {e}")
            return False, [f"导入失败: {str(e)}"] 